///////////////////////////////////////////////////////////////////////////////
//                   ALL STUDENTS COMPLETE THESE SECTIONS
// Main Class File:  P1.java
// File:             SymbolTable.java
// Semester:         CS536 Spring 2013
//
// Author:           Sean Bauer, sbauer@cs.wisc.edu
// CS Login:         sbauer
// Lecturer's Name:  Hasti
//
///////////////////////////////////////////////////////////////////////////////
import java.util.*;

/**
 * Class to represent a symbol table for use by compiler.  Essentially pairs 
 * identifiers declared in a program being compiled with information about each
 * identifier. Implemented using a list of hash maps that pair names & types.
 * 
 * @author Sean Bauer
 */
public class SymbolTable {
	
	/**
	 * Constructor initializes symbol table to hold one empty hash map
	 */
	public SymbolTable()
	{
		// Using a LinkedList here since we only do inserts/removes at the start
		// Don't need the extra functionality provided by ArrayList (or at least not yet)
		list_ = new LinkedList<HashMap<String, Symbol>>();
		list_.add(0, new HashMap<String, Symbol>());
	}
	
	/**
	 * Insert pair into first hash map of the list
	 * 
	 * @param name - identifier to be used as key paired with symbol
	 * @param sym - symbol to be inserted
	 * @throws DuplicateException - if name already exists in current hash map
	 * @throws EmptySymbolTableException - if symbol table is empty
	 */
	public void insert(String name, Symbol sym) throws DuplicateException, EmptySymbolTableException
	{
		// Check for empty symbol table
		if (list_.size() == 0) throw new EmptySymbolTableException();
		
		// Don't allow null strings or symbols	
    if (name == null || sym == null) throw new NullPointerException();
		
		// Check for duplicate, throw exception if found
		HashMap<String, Symbol> first_hashmap = list_.get(0);
		if ( first_hashmap.containsKey(name) ) throw new DuplicateException();
		
		// Insert
		first_hashmap.put(name, sym);
	}
	
	/**
	 * Adds an empty hash map to beginning of list
	 */
	public void addMap()
	{
		list_.add(0, new HashMap<String, Symbol>());
	}
	
	/**
	 * Lookup an identifier in the current (first) hash map
	 * 
	 * @param name - identifier to be used as key
	 * @return null if not found, otherwise the Symbol found
	 */
	public Symbol localLookup(String name)
	{
		if (list_.size() == 0) return null;
		
		// Return Symbol with key==name, will return null if not in map
		return list_.get(0).get(name);
	}
	
	/**
	 * Lookup an identifier in all possible hash maps 
	 * 
	 * @param name - identifier to be used as key
	 * @return null if not found, otherwise first Symbol found
	 */
	public Symbol globalLookup(String name)
	{
		if (list_.size() == 0) return null;
		
		// Iterate through all maps in list looking for name
		HashMap<String, Symbol> cur_map;
		for (int map_index = 0; map_index < list_.size(); map_index++)
		{
			cur_map = list_.get(map_index);
			if (cur_map.containsKey(name)) return cur_map.get(name);
		}
		// Reached end of list without returning, so no key==name in list
		return null;
	}
	
	/**
	 * Removes first hash map from list
	 * 
	 * @throws EmptySymbolTableException if list is empty
	 */
	public void removeMap() throws EmptySymbolTableException
	{
		if (list_.size() == 0) throw new EmptySymbolTableException();
		
		list_.remove(0);
	}
	
	/**
	 * Prints all hash map's contents
	 */
	public void print()
	{
		System.out.print("\nSymbol Table\n");
		
		for (int map_index = 0; map_index < list_.size(); map_index++)
		{
			System.out.println(list_.get(map_index).toString());
		}
    System.out.println();
	}

	private List<HashMap<String,Symbol>> list_;
}
